package info.ericbutler.microcosm.world;

import info.ericbutler.microcosm.entities.UpdatingGameElement;
import info.ericbutler.microcosm.enums.Direction;
import info.ericbutler.microcosm.enums.TileType;
import info.ericbutler.microcosm.game.MicrocosmMain;

import java.util.HashMap;
import java.util.HashSet;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.state.StateBasedGame;

public class Microcosm implements UpdatingGameElement {
	private GameSquare[][][]				tiles;
	public static int						TILE_SCALE_PIXELS	= 16;
	private int								rows				= 48, cols = 64, depth = 1, updateInterval = 15000, timer = updateInterval;
	private float							treeDensity			= 0.03f, grassDensity = 0.06f, bigTreeChance = 0.2f;
	private String							defaultTileType;
	private HashMap<GameSquare, Boolean>	isItIn				= new HashMap<>();
	
	public Microcosm() {
		isItIn.put( GameSquare.TALL_TREE1, true );
		isItIn.put( GameSquare.TREE1, true );
		isItIn.put( GameSquare.FANCY_GRASS1, true );
		isItIn.put( GameSquare.TALL_GRASS1, true );
		// Accommodate any screen
		TILE_SCALE_PIXELS = MicrocosmMain.SCREEN_WIDTH / 64;
		defaultTileType = "FANCY_GRASS1";
		tiles = new GameSquare[rows][cols][depth];
		for( int level = 0; level < depth; level++ ) {
			for( int row = 0; row < rows; row++ ) {
				for( int col = 0; col < cols; col++ ) {
					// Creates a grass world
					GameSquare tType;
					double roll1 = Math.random(), roll2 = Math.random();
					if( roll1 <= treeDensity ) {
						if( isItIn.get( GameSquare.TALL_TREE1 ) && roll2 <= bigTreeChance ) {
							tType = GameSquare.TALL_TREE1;
						} else {
							tType = GameSquare.TREE1;
						}
					} else if( isItIn.get( GameSquare.TALL_GRASS1 ) && roll1 <= treeDensity + grassDensity ) {
						tType = GameSquare.TALL_GRASS1;
					} else {
						if( isItIn.get( GameSquare.FANCY_GRASS1 ) ) {
							tType = GameSquare.FANCY_GRASS1;
						} else {
							tType = GameSquare.GRASS1;
						}
					}
					tiles[row][col][level] = tType.clone().setRow( row ).setCol( col ).setDepth( level ).setMyWorld( this );
				}
			}
		}
	}

	/** @param dir Direction Enum, cardinal direction.
	 * @param collisionBox The player's terrainCollisionBox
	 * @param distance Speed or distance to attempt to move.
	 * @return The (positive) amount of distance the character could move in that direction from 0 to distance(speed) passed in. */
	public int canMove(Direction dir, Rectangle collisionBox, double distance) {
		if( dir == Direction.NONE ) { return 0; }
		int goodToGo = 0;
		// Break collision box into appropriate points to test with, small enough intervals so that map tiles can't "slide through".
		/* DIAGRAM |----------| <-- CollisionBox #----#----# | | | | | | # # | | Box w/ Pts. ---> | | The area between points (#s) must be
		 * smaller than your map tile size |__________| #----#----# respective to the dimension ie top gaps < column scale. */
		int widthPts = (int)( Math.ceil( collisionBox.getWidth() / this.TILE_SCALE_PIXELS ) + 1 );
		int heightPts = (int)( Math.ceil( collisionBox.getHeight() / this.TILE_SCALE_PIXELS ) + 1 );
		double widthGap = collisionBox.getWidth() / ( widthPts - 1 );
		double heightGap = collisionBox.getHeight() / ( heightPts - 1 );
		int totalPts = 2 * heightPts + 2 * widthPts - 4;
		// Assemble suggested new position bounds to check, ie perimeter points of suggested movement
		HashSet<Vector2f> perimeterPoints = new HashSet<Vector2f>();
		for( int p = 0; p < totalPts; p++ ) {
			double px = 0, py = 0;
			if( p < widthPts ) {
				px = p * widthGap;
			} else if( p < widthPts + heightPts - 1 ) {
				px = collisionBox.getWidth();
				py = ( p - ( widthPts - 1 ) ) * heightGap;
			} else if( p < widthPts * 2 + heightPts - 2 ) {
				px = widthGap * ( ( widthPts - 1 ) - ( p - ( heightPts - 1 ) - ( widthPts - 1 ) ) );
				py = collisionBox.getHeight();
			} else {
				px = 0;
				py = heightGap * ( ( heightPts - 1 ) - ( p - ( heightPts - 1 ) - ( widthPts * 2 - 2 ) ) );
			}
			perimeterPoints.add( new Vector2f( (float)px + collisionBox.getMinX(), (float)py + collisionBox.getMinY() ) );
		}
		// Check each 'step' of our attempted movement from 1 - distance(ie speed)
		for( int i = 1; i <= distance; i++ ) {
			// Check each corner of this step
			for( Vector2f point : perimeterPoints ) {
				Vector2f pointCheck = point.copy().add( new Vector2f( dir.adjX() * i, dir.adjY() * i ) );
				// Out of bounds check
				if( pointCheck.y < 0 || pointCheck.y >= this.rows * Microcosm.TILE_SCALE_PIXELS || pointCheck.x < 0
						|| pointCheck.x >= this.cols * Microcosm.TILE_SCALE_PIXELS ) { return goodToGo; }
				// Figure out what map square this point falls on
				int checkX = (int)( pointCheck.x / Microcosm.TILE_SCALE_PIXELS );
				int checkY = (int)( pointCheck.y / Microcosm.TILE_SCALE_PIXELS );
				// If that's a wall square... no go, we're done here, return the number of steps that
				// were OKed so far
				if( !this.tiles[checkY][checkX][0].isPassable() ) { return goodToGo; }
			}
			// If this step, i, didn't have any conflicts on any of the proposed new corners, then
			// increment our ongoing count of how many steps are OK to take
			goodToGo++;
		}
		return goodToGo;
	}

	@Override
	public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException {
		for( int level = 0; level < depth; level++ ) {
			for( int row = 0; row < rows; row++ ) {
				for( int col = 0; col < cols; col++ ) {
					this.tiles[row][col][level].updateGrow( delta );
				}
			}
		}
	}

	@Override
	public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException {}

	/** Takes pixel coordinates and gives you the tile back
	 * @param pixelX
	 * @param pixelY */
	public GameSquare getTile(float pixelX, float pixelY) {
		int tileX = (int)pixelX / this.TILE_SCALE_PIXELS;
		int tileY = (int)pixelY / this.TILE_SCALE_PIXELS;
		return getTiles()[tileY][tileX][0];
	}

	public int areNeighborsOf( GameSquare centerSquare, TileType condition, boolean checkDiagonals ) {
		int found = 0;
		for( int x = -1; x <= 1; x++ ) {
			for( int y = -1; y <= 1; y++ ) {
				if( (!checkDiagonals && x != 0 && y != 0) || (x == 0 && y == 0) ) {
					continue;
				} else {
					// Ensure not out of index
					if( centerSquare.getRow() + x < 0 || centerSquare.getRow() + x >= this.tiles.length ||
							centerSquare.getCol() + y < 0 || centerSquare.getCol() + y >= this.tiles[0].length ) {
						continue;
					}
					if( this.tiles[centerSquare.getRow() + x][centerSquare.getCol() + y][0].getType() == condition ) {
						found++;
					}
				}
			}
		}
			
		return found;
	}
	/** Takes a map tile coordinate and sets the tile there to a new square
	 * @param x
	 * @param y
	 * @param z
	 * @param newSquare */
	public void setTile( int x, int y, int z, GameSquare newSquare ) {
		this.tiles[y][x][z] = newSquare;
	}

	@Override
	public Vector2f getCenter() {
		return null;
	}

	public GameSquare[][][] getTiles() {
		return tiles;
	}

	public int getWorldWidth() {
		return this.tiles[0].length;
	}
	
	public int getWorldHeight() {
		return this.tiles.length;
	}
	
	public void setTiles(GameSquare[][][] tiles) {
		this.tiles = tiles;
	}

	public float getTreeDensity() {
		return treeDensity;
	}

	public void setTreeDensity(float treeDensity) {
		this.treeDensity = treeDensity;
	}

	public String getDefaultTileType() {
		return defaultTileType;
	}

	public void setDefaultTileType(String defaultTileType) {
		this.defaultTileType = defaultTileType;
	}
}
